﻿using System;
using System.Runtime.InteropServices;
using System.Threading;

namespace WindowsAPI.Hooks
{
    /// <summary>Хук</summary>
    [Serializable]
    public partial class Hook : MarshalByRefObject, IDisposable
    {
        /* --------------------------------------------------------------------------------------------- */

        protected IntPtr f_HookHandle = IntPtr.Zero;
        private readonly HookType f_HookType;
        private readonly int f_ThreadId;
        protected readonly HookProc f_Proc;
        protected IntPtr f_ModuleHandle;
        /*
                private bool f_Enable;
        */

        /* --------------------------------------------------------------------------------------------- */

        public bool Enable { get { return f_HookHandle != IntPtr.Zero; } set { if(value) SetHook(); else ResetHook(); } }

        public IntPtr Handle { get { return f_HookHandle; } }

        public HookType Type { get { return f_HookType; } }

        public bool IsGlobal { get { return f_ThreadId == 0; } }

        /* --------------------------------------------------------------------------------------------- */

        public Hook(HookType Type, bool IsBlobal) : this(Type, IsBlobal ? 0 : Thread.CurrentThread.ManagedThreadId) { }

        public Hook(HookType Type, int ThreadId = 0)
        {
            if(ThreadId != 0 && Type == HookType.WH_SYSMSGFILTER)
                throw new ArgumentException("Нельзя установить " + Type + " как локальный", nameof(Type));

            f_HookType = Type;
            f_Proc = HookProcedure;
            f_ThreadId = ThreadId;
            f_ModuleHandle = Kernel32.GetModuleHandle(f_Proc.Method.Module.Name);
        }

        /* --------------------------------------------------------------------------------------------- */

        private void SetHook()
        {
            if(Enable) return;
            lock(this)
            {
                if(Enable) return;

                //f_ModuleHandle = f_ThreadId == 0
                //    ? User32.Hook.SetWindowsHookEx(f_HookType, f_Proc, f_ModuleHandle, 0)         //Глобальный хук
                //    : User32.Hook.SetWindowsHookEx(f_HookType, f_Proc, IntPtr.Zero, f_ThreadId);  //Локальный хук

                f_HookHandle = User32.Hook.SetWindowsHookEx(
                    f_HookType,
                    f_Proc,
                    f_ThreadId == 0 ? f_ModuleHandle : IntPtr.Zero,
                    f_ThreadId);

                if(f_HookHandle == IntPtr.Zero)
                    Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
            }
            if(Enable)
                OnEnableChanged();
        }

        private void ResetHook()
        {
            if(!Enable) return;
            lock(this)
            {
                if(!Enable) return;
                if(User32.Hook.UnhookWindowsHookEx(f_HookHandle))
                    f_HookHandle = IntPtr.Zero;
            }
            if(!Enable)
                OnEnableChanged();
        }

        private IntPtr HookProcedure(int Code, IntPtr wParams, IntPtr lParams)
        {
            if(Code >= 0)
            {
                //Console.Beep();
                IntPtr lv_Result;
                if(OnEvent(Code, wParams, lParams, out lv_Result))
                    return lv_Result;
            }
            return User32.Hook.CallNextHookEx(f_HookHandle, Code, wParams, lParams);
        }

        /* --------------------------------------------------------------------------------------------- */

        #region IDisposable Members

        public void Dispose() { ResetHook(); }

        void IDisposable.Dispose() { Dispose(); }

        #endregion

        public override string ToString()
        {
            return Enable
                ? string.Format("{2}({0}:{1})", Type, Handle, IsGlobal ? "Global hook" : "Hook")
                 : string.Format("{1}({0})", Type, IsGlobal ? "Global hook" : "Hook");
        }

        /* --------------------------------------------------------------------------------------------- */
    }
}
